package nsfs

import (
	"GLibHac/fssystem"
	"GLibHac/tools"
	"errors"
)

var StopEnumerateErr = errors.New("stop enumerate")

type PartitionFileSystemType struct {
	Magic     string
	EntrySize int32
}

var PartitionFileSystemTypeStandard = PartitionFileSystemType{"PFS0", 24}
var PartitionFileSystemTypeHashed = PartitionFileSystemType{"HFS0", 64}

func ConvertToPartitionFileSystemType(magic string) *PartitionFileSystemType {
	if PartitionFileSystemTypeStandard.Magic == magic {
		return &PartitionFileSystemTypeStandard
	} else if PartitionFileSystemTypeHashed.Magic == magic {
		return &PartitionFileSystemTypeHashed
	} else {
		return nil
	}
}

type SearchOption uint8

const (
	SearchOptionDefault               = SearchOption(iota)
	SearchOptionRecurseSubdirectories = SearchOption(iota)
	SearchOptionCaseInsensitive       = SearchOption(iota)
)

func (m SearchOption) HasFlag(mode SearchOption) bool {
	return (m & mode) > 0
}

type IteratorDirectoryFunc func(entry *DirectoryEntryEx) error

type IFileSystem interface {
	OpenFile(filePath string, openFileMode OpenFileMode) (fssystem.IStorage, error)
	OpenDirectory(path string, openDirectoryMode OpenDirectoryMode) (IDirectory, error)
	EnumerateEntries(path, searchPattern string, yieldFunc IteratorDirectoryFunc) error
	EnumerateEntriesWithOption(path, searchPattern string, options SearchOption, yieldFunc IteratorDirectoryFunc) error
}

type iFileSystemAbstract struct {
	openFileFunc  func(filePath string, openFileMode OpenFileMode) (fssystem.IStorage, error)
	openDirectory func(path string, openDirectoryMode OpenDirectoryMode) (IDirectory, error)
}

func (i *iFileSystemAbstract) OpenFile(filePath string, openFileMode OpenFileMode) (fssystem.IStorage, error) {
	return i.openFileFunc(filePath, openFileMode)
}

func (i *iFileSystemAbstract) OpenDirectory(path string, openDirectoryMode OpenDirectoryMode) (IDirectory, error) {
	return i.openDirectory(path, openDirectoryMode)
}

func (i *iFileSystemAbstract) EnumerateEntries(path, searchPattern string, yieldFunc IteratorDirectoryFunc) error {
	return i.EnumerateEntriesWithOption(path, searchPattern, SearchOptionRecurseSubdirectories, yieldFunc)
}

func (i *iFileSystemAbstract) EnumerateEntriesWithOption(path, searchPattern string, searchOptions SearchOption, yieldFunc IteratorDirectoryFunc) error {
	ignoreCase := SearchOptionCaseInsensitive.HasFlag(searchOptions)
	recurse := SearchOptionRecurseSubdirectories.HasFlag(searchOptions)
	directory, err := i.OpenDirectory(path, OpenDirectoryModeAll)
	if err != nil {
		return err
	}
	dirEntry := make([]*DirectoryEntry, 1)
	for {
		read, err := directory.Read(dirEntry)
		if err != nil {
			return err
		}
		if read <= 0 {
			break
		}
		entry := getDirectoryEntryEx(dirEntry[0], path)
		if ok, err := tools.PathMatchesPattern(searchPattern, entry.Name, ignoreCase); err != nil {
			return err
		} else if !ok {
			continue
		}
		err = yieldFunc(entry)
		if err != nil {
			if errors.Is(err, StopEnumerateErr) {
				return nil
			}
			return err
		}
		if entry.Type != DirectoryEntryTypeDirectory || !recurse {
			continue
		}
		if err = i.EnumerateEntriesWithOption(tools.PathCombine(path, entry.Name), searchPattern, searchOptions, yieldFunc); err != nil {
			return err
		}
	}
	return nil
}

func getDirectoryEntryEx(entry *DirectoryEntry, parentPath string) *DirectoryEntryEx {
	path := tools.PathCombine(parentPath, entry.Name)
	entryEx := NewDirectoryEntryEx(entry.Name, path, entry.Type, entry.Size)
	entryEx.NxFileAttributes = entry.Attributes
	return entryEx
}
